/*
 * UAE - The Un*x Amiga Emulator
 *
 * WIN32 CDROM/HD low level access code (SPTI)
 *
 * Copyright 2002-2010 Toni Wilen
 *
 */

#include "sysconfig.h"
#include "blkdev.h"
#include "gui.h"
#include "thread.h"
#include "newcpu.h"
#include "traps.h"
#include "scsidev.h"

#define INQUIRY_SIZE 36

struct SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER
{
    SCSI_PASS_THROUGH_DIRECT spt;
    ULONG Filler;
    UCHAR SenseBuf[32];
};

struct dev_info_spti
{
    TCHAR* drvpath;
    TCHAR* name;
    byte* inquirydata;
    TCHAR* ident;
    TCHAR drvletter;
    TCHAR drvlettername[10];
    int mediainserted;
    HANDLE handle;
    int isatapi;
    int removable;
    int type;
    int bus, path, target, lun;
    int scanmode;
    byte* scsibuf;
    bool open;
    bool enabled;
    struct device_info di;
};

static uae_sem_t scgp_sem;
static struct dev_info_spti dev_info[MAX_TOTAL_SCSI_DEVICES];
static int unittable[MAX_TOTAL_SCSI_DEVICES];
static int total_devices;
static int bus_open;

static int getunitnum(struct dev_info_spti* di)
{
    if (!di)
        return -1;
    int idx = di - &dev_info[0];
    for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++)
    {
        if (unittable[i] - 1 == idx)
            return i;
    }
    return -1;
}

static struct dev_info_spti* unitcheck(int unitnum)
{
    if (unitnum < 0 || unitnum >= MAX_TOTAL_SCSI_DEVICES)
        return nullptr;
    if (unittable[unitnum] <= 0)
        return nullptr;
    unitnum = unittable[unitnum] - 1;
    //  if (dev_info[unitnum].drvletter == 0)
    //          return nullptr;
    return &dev_info[unitnum];
}

static struct dev_info_spti* unitisopen(int unitnum)
{
    struct dev_info_spti* di = unitcheck(unitnum);
    if (!di)
        return nullptr;
    if (di->open == false)
        return nullptr;
    return di;
}

static int doscsi(struct dev_info_spti* di, int unitnum, SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER* swb, int* err)
{
    DWORD status, returned;

    *err = 0;
    if (Logging::SCSI_DEBUG)
    {
        Logger::Write(L"SCSI, H=%X:%d:%d:%d:%d: ", di->handle, di->bus, di->path, di->target, di->lun);
        scsi_log_before(swb->spt.Cdb, swb->spt.CdbLength,
            swb->spt.DataIn == SCSI_IOCTL_DATA_OUT ? (byte*)swb->spt.DataBuffer : nullptr, swb->spt.DataTransferLength);
    }
    gui_flicker_led(LED_CD, unitnum, 1);
    swb->spt.ScsiStatus = 0;
    if (di->bus >= 0)
    {
        swb->spt.PathId = di->path;
        swb->spt.TargetId = di->target;
        swb->spt.Lun = di->lun;
    }
    status = DeviceIoControl(di->handle, IOCTL_SCSI_PASS_THROUGH_DIRECT,
        swb, sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER),
        swb, sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER),
        &returned, nullptr);
    if (!status)
    {
        int lasterror = GetLastError();
        *err = lasterror;
        Logger::Write(L"SCSI ERROR, H=%X:%d:%d:%d:%d: ", di->handle, di->bus, di->path, di->target, di->lun);
        Logger::Write(L"Status = %d, Error code = %d, LastError=%d\n", status, swb->spt.ScsiStatus, lasterror);
        scsi_log_before(swb->spt.Cdb, swb->spt.CdbLength,
            swb->spt.DataIn == SCSI_IOCTL_DATA_OUT ? (byte*)swb->spt.DataBuffer : 0, swb->spt.DataTransferLength);
    }
    if (Logging::SCSI_DEBUG)
        scsi_log_after(swb->spt.DataIn == SCSI_IOCTL_DATA_IN ? (byte*)swb->spt.DataBuffer : nullptr, swb->spt.DataTransferLength,
            swb->SenseBuf, swb->spt.SenseInfoLength);
    if (swb->spt.SenseInfoLength > 0 && (swb->SenseBuf[0] == 0 || swb->SenseBuf[0] == 1))
        swb->spt.SenseInfoLength = 0;  /* 0 and 1 = success, not error.. */
    if (swb->spt.SenseInfoLength > 0)
        return 0;
    gui_flicker_led(LED_CD, unitnum, 1);
    return status;
}

#define MODE_SELECT_6 0x15
#define MODE_SENSE_6 0x1A
#define MODE_SELECT_10 0x55
#define MODE_SENSE_10 0x5A

static int execscsicmd(struct dev_info_spti* di, int unitnum, byte* data, int len, byte* inbuf, int inlen)
{
    SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER swb;
    DWORD status;
    int err, dolen;

    uae_sem_wait(&scgp_sem);
    memset(&swb, 0, sizeof(swb));
    swb.spt.Length = sizeof(SCSI_PASS_THROUGH);
    swb.spt.CdbLength = len;
    if (inbuf)
    {
        swb.spt.DataIn = SCSI_IOCTL_DATA_IN;
        swb.spt.DataTransferLength = inlen;
        swb.spt.DataBuffer = inbuf;
        memset(inbuf, 0, inlen);
    }
    else
    {
        swb.spt.DataIn = SCSI_IOCTL_DATA_OUT;
    }
    swb.spt.TimeOutValue = 80 * 60;
    swb.spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, SenseBuf);
    swb.spt.SenseInfoLength = 32;
    memcpy(swb.spt.Cdb, data, len);
    status = doscsi(di, unitnum, &swb, &err);
    uae_sem_post(&scgp_sem);
    dolen = swb.spt.DataTransferLength;
    if (!status)
        return -1;
    return dolen;
}

static int execscsicmd_direct(int unitnum, struct amigascsi* as)
{
    struct dev_info_spti* di = unitisopen(unitnum);
    if (!di)
        return -1;

    SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER swb;
    DWORD status;
    int sactual = 0, i;
    int io_error = 0, err, parm = 0;
    byte* scsi_datap, * scsi_datap_org;
    uint scsi_cmd_len_orig = as->cmd_len;

    memset(&swb, 0, sizeof(swb));
    swb.spt.Length = sizeof(SCSI_PASS_THROUGH);
    swb.spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, SenseBuf);

    if (as->len > DEVICE_SCSI_BUFSIZE)
        as->len = DEVICE_SCSI_BUFSIZE;

    uae_sem_wait(&scgp_sem);

    /* the Amiga does not tell us how long the timeout shall be, so make it _very_ long (specified in seconds) */
    swb.spt.TimeOutValue = 80 * 60;
    scsi_datap = scsi_datap_org = as->len ? as->data : 0;
    swb.spt.DataIn = (as->flags & 1) ? SCSI_IOCTL_DATA_IN : SCSI_IOCTL_DATA_OUT;
    for (i = 0; i < as->cmd_len; i++)
        swb.spt.Cdb[i] = as->cmd[i];
    if (as->sense_len > 32)
        as->sense_len = 32;
    swb.spt.SenseInfoLength = (as->flags & 4) ? 4 :  /* SCSIF_OLDAUTOSENSE */
        (as->flags & 2) ? as->sense_len : /* SCSIF_AUTOSENSE */ 32;
    if (dev_info[unitnum].isatapi)
        scsi_atapi_fixup_pre(swb.spt.Cdb, &as->cmd_len, &scsi_datap, &as->len, &parm);

    memcpy(di->scsibuf, scsi_datap, as->len);

    swb.spt.CdbLength = (UCHAR)as->cmd_len;
    swb.spt.DataTransferLength = as->len;
    swb.spt.DataBuffer = di->scsibuf;

    status = doscsi(di, unitnum, &swb, &err);

    memcpy(scsi_datap, di->scsibuf, as->len);

    as->cmdactual = status == 0 ? 0 : scsi_cmd_len_orig; /* fake scsi_CmdActual */
    as->status = swb.spt.ScsiStatus; /* scsi_Status */
    if (swb.spt.ScsiStatus)
    {
        io_error = 45; /* HFERR_BadStatus */
        /* copy sense? */
        for (sactual = 0; sactual < as->sense_len && sactual < swb.spt.SenseInfoLength; sactual++)
            as->sensedata[sactual] = swb.SenseBuf[sactual];
        as->actual = 0; /* scsi_Actual */
    }
    else
    {
        int i;
        for (i = 0; i < as->sense_len; i++)
            as->sensedata[i] = 0;
        sactual = 0;
        if (status == 0)
        {
            io_error = 20; /* io_Error, but not specified */
            as->actual = 0; /* scsi_Actual */
        }
        else
        {
            as->len = swb.spt.DataTransferLength;
            if (dev_info[unitnum].isatapi)
                scsi_atapi_fixup_post(swb.spt.Cdb, as->cmd_len, scsi_datap_org, scsi_datap, &as->len, parm);
            io_error = 0;
            as->actual = as->len; /* scsi_Actual */
        }
    }
    as->sactual = sactual;
    uae_sem_post(&scgp_sem);

    if (scsi_datap != scsi_datap_org)
        free(scsi_datap);

    return io_error;
}

static byte* execscsicmd_out(int unitnum, byte* data, int len)
{
    struct dev_info_spti* di = unitisopen(unitnum);
    if (!di)
        return 0;
    int v = execscsicmd(di, unitnum, data, len, 0, 0);
    if (v < 0)
        return 0;
    return data;
}

static byte* execscsicmd_in(int unitnum, byte* data, int len, int* outlen)
{
    struct dev_info_spti* di = unitisopen(unitnum);
    if (!di)
        return 0;
    int v = execscsicmd(di, unitnum, data, len, di->scsibuf, DEVICE_SCSI_BUFSIZE);
    if (v < 0)
        return 0;
    if (v == 0)
        return 0;
    if (outlen)
        *outlen = v < *outlen ? v : *outlen;
    return di->scsibuf;
}

static byte* execscsicmd_in_internal(struct dev_info_spti* di, int unitnum, byte* data, int len, int* outlen)
{
    int v = execscsicmd(di, unitnum, data, len, di->scsibuf, DEVICE_SCSI_BUFSIZE);
    if (v < 0)
        return 0;
    if (v == 0)
        return 0;
    if (outlen)
        *outlen = v;
    return di->scsibuf;
}

static void close_scsi_device2(struct dev_info_spti* di)
{
    if (di->open == false)
        return;
    if (di->handle != INVALID_HANDLE_VALUE)
        CloseHandle(di->handle);
    di->handle = INVALID_HANDLE_VALUE;
    di->open = false;
}

static void close_scsi_device(int unitnum)
{
    struct dev_info_spti* di = unitisopen(unitnum);
    if (!di)
        return;
    close_scsi_device2(di);
    blkdev_cd_change(unitnum, di->drvletter ? di->drvlettername : di->name);
    unittable[unitnum] = 0;
}

static void free_scsi_device(struct dev_info_spti* di)
{
    close_scsi_device2(di);
    free(di->name);
    free(di->drvpath);
    free(di->inquirydata);
    VirtualFree(di->scsibuf, 0, MEM_RELEASE);
    di->name = nullptr;
    di->drvpath = nullptr;
    di->inquirydata = nullptr;
    di->scsibuf = nullptr;
    memset(di, 0, sizeof(struct dev_info_spti));
}

static int rescan();

static void close_scsi_bus()
{
    if (!bus_open)
    {
        Logger::Write(L"SPTI close_bus() when already closed!\n");
        return;
    }
    for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++)
    {
        free_scsi_device(&dev_info[i]);
        unittable[i] = 0;
    }
    total_devices = 0;
    bus_open = 0;
    Logger::Write(L"SPTI driver closed.\n");
}

static int open_scsi_bus(int flags)
{
    if (bus_open)
    {
        Logger::Write(L"SPTI open_bus() more than once!\n");
        return 1;
    }
    total_devices = 0;
    uae_sem_init(&scgp_sem, 0, 1);
    rescan();
    bus_open = 1;
    Logger::Write(L"SPTI driver open, %d devices.\n", total_devices);
    return total_devices;
}

static int mediacheck(struct dev_info_spti* di, int unitnum)
{
    byte cmd[6] = { 0, 0, 0, 0, 0, 0 }; /* TEST UNIT READY */
    if (di->open == false)
        return -1;
    int v = execscsicmd(di, unitnum, cmd, sizeof cmd, 0, 0);
    return v >= 0 ? 1 : 0;
}

static int mediacheck_full(struct dev_info_spti* di, int unitnum, struct device_info* dinfo)
{
    byte cmd1[10] = { 0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; /* READ CAPACITY */
    byte* p;
    int outlen;

    dinfo->sectorspertrack = 0;
    dinfo->trackspercylinder = 0;
    dinfo->bytespersector = 0;
    dinfo->cylinders = 0;
    dinfo->write_protected = 1;
    if (di->open == false)
        return 0;
    outlen = 32;
    p = execscsicmd_in_internal(di, unitnum, cmd1, sizeof cmd1, &outlen);
    if (p && outlen >= 8)
    {
        dinfo->bytespersector = (p[4] << 24) | (p[5] << 16) | (p[6] << 8) | p[7];
        dinfo->sectorspertrack = ((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]) + 1;
        dinfo->trackspercylinder = 1;
        dinfo->cylinders = 1;
    }
    if (di->type == INQ_DASD)
    {
        byte cmd2[10] = { 0x5a, 0x08, 0, 0, 0, 0, 0, 0, 0xf0, 0 }; /* MODE SENSE */
        outlen = 32;
        p = execscsicmd_in_internal(di, unitnum, cmd2, sizeof cmd2, &outlen);
        if (p && outlen >= 4)
        {
            dinfo->write_protected = (p[3] & 0x80) ? 1 : 0;
        }
    }
    //  Logger::Write (L"mediacheck_full(%d,%d,%d,%d,%d)\n",
    //  di->bytespersector, di->sectorspertrack, di->trackspercylinder, di->cylinders, di->write_protected);
    return 1;
}

static void update_device_info(int unitnum)
{
    struct dev_info_spti* dispti = unitisopen(unitnum);
    if (!dispti)
        return;
    struct device_info* di = &dispti->di;
    _tcscpy(di->label, dispti->drvletter ? dispti->drvlettername : dispti->name);
    _tcscpy(di->mediapath, dispti->drvpath);
    di->bus = 0;
    di->target = unitnum;
    di->lun = 0;
    di->media_inserted = mediacheck(dispti, unitnum);
    di->removable = dispti->removable;
    mediacheck_full(dispti, unitnum, di);
    di->type = dispti->type;
    di->unitnum = unitnum + 1;
    di->backend = L"SPTI";
    if (Logging::SCSI_DEBUG)
    {
        Logger::Write(L"MI=%d TP=%d WP=%d CY=%d BK=%d RMB=%d '%s'\n",
            di->media_inserted, di->type, di->write_protected, di->cylinders, di->bytespersector, di->removable, di->label);
    }
}

static void checkcapabilities(struct dev_info_spti* di)
{
    STORAGE_ADAPTER_DESCRIPTOR desc;
    STORAGE_PROPERTY_QUERY query;
    DWORD ret, status;

    memset(&query, 0, sizeof STORAGE_PROPERTY_QUERY);
    query.PropertyId = StorageAdapterProperty;
    query.QueryType = PropertyStandardQuery;
    status = DeviceIoControl(di->handle, IOCTL_STORAGE_QUERY_PROPERTY,
        &query, sizeof query, &desc, sizeof desc, &ret, nullptr);
    if (status)
    {
        if (desc.Version > offsetof(STORAGE_ADAPTER_DESCRIPTOR, BusType))
            Logger::Write(L"SCSI CAPS: BusType=%d, MaxTransfer=0x%08X, Mask=0x%08X\n",
                desc.BusType, desc.MaximumTransferLength, desc.AlignmentMask);
    }
}

static int inquiry(struct dev_info_spti* di, int unitnum, byte* inquirydata)
{
    byte cmd[6] = { 0x12, 0, 0, 0, 36, 0 }; /* INQUIRY */
    // byte out[INQUIRY_SIZE] = { 0 };
    int outlen = sizeof(byte[INQUIRY_SIZE]);
    byte* p = execscsicmd_in_internal(di, unitnum, cmd, sizeof(cmd), &outlen);
    int inqlen = 0;

    di->isatapi = 0;
    di->removable = 0;
    di->type = 0x1f;
    if (!p)
    {
        if (Logging::SCSI_DEBUG)
            Logger::Write(L"SPTI: INQUIRY failed\n");
        return 0;
    }
    inqlen = outlen > INQUIRY_SIZE ? INQUIRY_SIZE : outlen;
    if (outlen >= 1)
    {
        di->type = p[0] & 31;
        di->removable = (p[1] & 0x80) ? 1 : 0;
    }
    if (outlen >= 2 && (p[0] & 31) == 5 && (p[2] & 7) == 0)
        di->isatapi = 1;
    memcpy(inquirydata, p, inqlen);
    if (Logging::SCSI_DEBUG)
    {
        if (outlen >= INQUIRY_SIZE)
        {
            char tmp[20];
            TCHAR* s1, * s2;

            memcpy(tmp, p + 8, 8);
            tmp[8] = 0;
            s1 = Unicode::au(tmp);
            memcpy(tmp, p + 16, 16);
            tmp[16] = 0;
            s2 = Unicode::au(tmp);
            Logger::Write(L"SPTI: INQUIRY: %02X%02X%02X %d '%s' '%s'\n",
                p[0], p[1], p[2], di->isatapi, s1, s2);
            free(s2);
            free(s1);
        }
    }
    return inqlen;
}

static int open_scsi_device2(struct dev_info_spti* di, int unitnum)
{
    HANDLE h;
    TCHAR* dev;

    if (di->bus >= 0)
    {
        dev = xmalloc(TCHAR, 100);
        _stprintf(dev, L"\\\\.\\Scsi%d:", di->bus);
    }
    else
    {
        dev = _tcsdup(di->drvpath);
    }
    if (!di->scsibuf)
        di->scsibuf = (byte*)VirtualAlloc(nullptr, DEVICE_SCSI_BUFSIZE, MEM_COMMIT, PAGE_READWRITE);
    h = CreateFile(dev, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
    di->handle = h;
    if (h == INVALID_HANDLE_VALUE)
    {
        Logger::Write(L"SPTI: failed to open unit %d err=%d ('%s')\n", unitnum, GetLastError(), dev);
    }
    else
    {
        byte inqdata[INQUIRY_SIZE + 1] = { 0 };
        checkcapabilities(di);
        if (!inquiry(di, unitnum, inqdata))
        {
            Logger::Write(L"SPTI: inquiry failed unit %d ('%s':%d:%d:%d:%d)\n", unitnum, dev,
                di->bus, di->path, di->target, di->lun);
            close_scsi_device2(di);
            free(dev);
            return 0;
        }
        inqdata[INQUIRY_SIZE] = 0;
        di->name = Unicode::au((char*)inqdata + 8);
        if (di->type == INQ_ROMD)
        {
            di->mediainserted = mediacheck(di, unitnum);
            Logger::Write(L"SPTI: unit %d (%c:\\) opened [%s], %s, '%s'\n",
                unitnum, di->drvletter ? di->drvletter : '*',
                di->isatapi ? L"ATAPI" : L"SCSI",
                di->mediainserted ? L"media inserted" : L"drive empty",
                di->name);
        }
        else
        {
            Logger::Write(L"SPTI: unit %d, type %d, '%s'\n",
                unitnum, di->type, di->name);
        }
        di->inquirydata = xmalloc(byte, INQUIRY_SIZE);
        memcpy(di->inquirydata, inqdata, INQUIRY_SIZE);
        free(dev);
        di->open = true;
        update_device_info(unitnum);
        if (di->type == INQ_ROMD)
            blkdev_cd_change(unitnum, di->drvletter ? di->drvlettername : di->name);
        return 1;
    }
    free(dev);
    return 0;
}

int open_scsi_device(int unitnum, const TCHAR* ident, int flags)
{
    struct dev_info_spti* di = nullptr;
    if (ident && ident[0])
    {
        for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++)
        {
            di = &dev_info[i];
            if (unittable[i] == 0 && di->drvletter != 0)
            {
                if (!_tcsicmp(di->drvlettername, ident))
                {
                    unittable[unitnum] = i + 1;
                    if (open_scsi_device2(di, unitnum))
                        return 1;
                    unittable[unitnum] = 0;
                    return 0;
                }
            }
        }
        return 0;
    }
    di = &dev_info[unitnum];
    for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++)
    {
        if (unittable[i] == unitnum + 1)
            return 0;
    }
    if (di->enabled == 0)
        return 0;
    unittable[unitnum] = unitnum + 1;
    if (open_scsi_device2(di, unitnum))
        return 1;
    unittable[unitnum] = 0;
    return 0;
}

static int adddrive(const TCHAR* drvpath, int bus, int pathid, int targetid, int lunid, int scanmode)
{
    struct dev_info_spti* di;
    int cnt = total_devices, i;
    int freeit = 1;

    if (cnt >= MAX_TOTAL_SCSI_DEVICES)
        return 0;
    for (i = 0; i < total_devices; i++)
    {
        di = &dev_info[i];
        if (!_tcscmp(drvpath, di->drvpath))
            return 0;
    }
    Logger::Write(L"SPTI: unit %d '%s' added\n", total_devices, drvpath);
    di = &dev_info[total_devices];
    di->drvpath = _tcsdup(drvpath);
    di->type = 0;
    di->bus = bus;
    di->path = pathid;
    di->target = targetid;
    di->lun = lunid;
    di->scanmode = scanmode;
    di->drvletter = 0;
    di->enabled = true;

    for (TCHAR drvletter = 'C'; drvletter <= 'Z'; drvletter++)
    {
        TCHAR drvname[10];
        TCHAR volname[MAX_DPATH], volname2[MAX_DPATH];
        _stprintf(drvname, L"%c:\\", drvletter);
        if (GetVolumeNameForVolumeMountPoint(drvname, volname, sizeof volname / sizeof(TCHAR)))
        {
            TCHAR drvpath2[MAX_DPATH];
            _stprintf(drvpath2, L"%s\\", di->drvpath);
            if (GetVolumeNameForVolumeMountPoint(drvpath2, volname2, sizeof volname2 / sizeof(TCHAR)))
            {
                if (!_tcscmp(volname, volname2))
                {
                    di->drvletter = drvletter;
                    _tcscpy(di->drvlettername, drvname);
                    break;
                }
            }
        }
    }

    total_devices++;
    unittable[cnt] = cnt + 1;
    if (open_scsi_device2(&dev_info[cnt], cnt))
    {
        for (i = 0; i < cnt; i++)
        {
            if (!memcmp(di->inquirydata, dev_info[i].inquirydata, INQUIRY_SIZE) && di->scanmode != dev_info[i].scanmode)
            {
                Logger::Write(L"duplicate device, skipped..\n");
                break;
            }
        }
        if (i == cnt)
        {
            freeit = 0;
            close_scsi_device2(&dev_info[cnt]);
        }
    }
    if (freeit)
    {
        free_scsi_device(&dev_info[cnt]);
        total_devices--;
    }
    unittable[cnt] = 0;
    return 1;
}

static struct device_info* info_device(int unitnum, struct device_info* di, int quick)
{
    struct dev_info_spti* dispti = unitcheck(unitnum);
    if (!dispti)
        return nullptr;
    if (!quick)
        update_device_info(unitnum);
    dispti->di.open = di->open;
    memcpy(di, &dispti->di, sizeof(struct device_info));
    return di;
}

void win32_spti_media_change(TCHAR driveletter, int insert)
{
    for (int i = 0; i < total_devices; i++)
    {
        struct dev_info_spti* di = &dev_info[i];
        if (di->drvletter == driveletter && di->mediainserted != insert)
        {
            Logger::Write(L"SPTI: media change %c %d\n", dev_info[i].drvletter, insert);
            di->mediainserted = insert;
            int unitnum = getunitnum(di);
            if (unitnum >= 0)
            {
                update_device_info(unitnum);
                scsi_do_disk_change(unitnum, insert, nullptr);
                blkdev_cd_change(unitnum, di->drvletter ? di->drvlettername : di->name);
            }
        }
    }
}

static int check_isatapi(int unitnum)
{
    struct dev_info_spti* di = unitcheck(unitnum);
    if (!di)
        return 0;
    return di->isatapi;
}

static int getCDROMProperty(int idx, HDEVINFO DevInfo, const GUID* guid)
{
    SP_DEVICE_INTERFACE_DATA interfaceData;
    PSP_DEVICE_INTERFACE_DETAIL_DATA interfaceDetailData = nullptr;
    DWORD interfaceDetailDataSize, reqSize;
    DWORD status, errorCode;

    interfaceData.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);
    status = SetupDiEnumDeviceInterfaces(
        DevInfo,    // Interface Device Info handle
        0,      // Device Info data
        guid,       // Interface registered by driver
        idx,        // Member
        &interfaceData  // Device Interface Data
        );
    if (status == FALSE)
        return FALSE;

    status = SetupDiGetDeviceInterfaceDetail(
        DevInfo,    // Interface Device info handle
        &interfaceData, // Interface data for the event class
        nullptr,        // Checking for buffer size
        0,      // Checking for buffer size
        &reqSize,   // Buffer size required to get the detail data
        nullptr     // Checking for buffer size
        );

    if (status == FALSE)
    {
        errorCode = GetLastError();
        if (errorCode != ERROR_INSUFFICIENT_BUFFER)
            return FALSE;
    }

    interfaceDetailDataSize = reqSize;
    interfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)xmalloc(byte, interfaceDetailDataSize);
    if (interfaceDetailData == nullptr)
        return FALSE;
    interfaceDetailData->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);

    status = SetupDiGetDeviceInterfaceDetail(
        DevInfo,        // Interface Device info handle
        &interfaceData,     // Interface data for the event class
        interfaceDetailData,    // Interface detail data
        interfaceDetailDataSize, // Interface detail data size
        &reqSize,       // Buffer size required to get the detail data
        nullptr);           // Interface device info

    if (status == FALSE)
        return FALSE;

    adddrive(interfaceDetailData->DevicePath, -1, -1, -1, -1, 1);

    free(interfaceDetailData);

    return TRUE;
}

#define SCSI_INFO_BUFFER_SIZE 0x5000
static void scanscsi()
{
    PSCSI_BUS_DATA BusData;
    PSCSI_INQUIRY_DATA InquiryData;
    PSCSI_ADAPTER_BUS_INFO AdapterInfo;
    HANDLE h;
    BOOL status;
    BOOL Claimed;
    ULONG returnedLength;
    SHORT Bus, Luns;
    DWORD bytesTransferred;
    int idx;
    TCHAR DeviceName[256];

    AdapterInfo = (PSCSI_ADAPTER_BUS_INFO)xmalloc(byte, SCSI_INFO_BUFFER_SIZE);
    if (AdapterInfo == nullptr)
        return;

    idx = 0;
    for (;;)
    {
        _stprintf(DeviceName, L"\\\\.\\Scsi%d:", idx++);
        h = CreateFile(DeviceName,
            GENERIC_READ | GENERIC_WRITE,
            0,
            nullptr,            // no SECURITY_ATTRIBUTES structure
            OPEN_EXISTING,            // No special create flags
            0,            // No special attributes
            nullptr);
        if (h == INVALID_HANDLE_VALUE)
            return;

        if (!DeviceIoControl(h,
                IOCTL_SCSI_RESCAN_BUS,
                nullptr,
                0,
                nullptr,
                0,
                &bytesTransferred,
                nullptr))
        {
            Logger::Write(L"Rescan SCSI port %d failed [Error %d]\n", idx - 1, GetLastError());
            CloseHandle(h);
            continue;
        }

        //  Get the SCSI inquiry data for all devices for the given SCSI bus
        status = DeviceIoControl(
            h,
            IOCTL_SCSI_GET_INQUIRY_DATA,
            nullptr,
            0,
            AdapterInfo,
            SCSI_INFO_BUFFER_SIZE,
            &returnedLength,
            nullptr);

        if (!status)
        {
            Logger::Write(L"Error in IOCTL_SCSI_GET_INQUIRY_DATA\n");
            CloseHandle(h);
            continue;
        }

        for (Bus = 0; Bus < AdapterInfo->NumberOfBuses; Bus++)
        {
            int luncheck = 0;
            BusData = &AdapterInfo->BusData[Bus];
            InquiryData = (PSCSI_INQUIRY_DATA)((PUCHAR)AdapterInfo + BusData->InquiryDataOffset);
            for (Luns = 0; Luns < BusData->NumberOfLogicalUnits; Luns++)
            {
                TCHAR label[100];
                int type = InquiryData->InquiryData[0] & 0x1f;
                Claimed = InquiryData->DeviceClaimed;
                Logger::Write(L"SCSI=%d Initiator=%d Path=%d Target=%d LUN=%d Claimed=%s Type=%d\n",
                    idx - 1,
                    BusData->InitiatorBusId, InquiryData->PathId, InquiryData->TargetId,
                    InquiryData->Lun, Claimed ? L"Yes" : L"No ", type);
                if (Claimed == 0 && !luncheck)
                {
                    luncheck = 1;
                    _stprintf(label, L"SCSI(%d):%d:%d:%d:%d", idx - 1, BusData->InitiatorBusId,
                        InquiryData->PathId, InquiryData->TargetId, InquiryData->Lun);
                    adddrive(label, idx - 1, InquiryData->PathId, InquiryData->TargetId, InquiryData->Lun, 3);
                }
                InquiryData = (PSCSI_INQUIRY_DATA)((PUCHAR)AdapterInfo + InquiryData->NextInquiryDataOffset);
            }   // for Luns
        }   // for Bus
        CloseHandle(h);
    }
}

static const GUID* guids[] = {
    &GUID_DEVINTERFACE_CDROM,
    &GUID_DEVCLASS_IMAGE,
    &GUID_DEVCLASS_TAPEDRIVE,
    nullptr
};
static const TCHAR* scsinames[] = { L"Tape", L"Scanner", L"Changer", nullptr };

static int rescan()
{
    int idx, idx2;

    for (idx2 = 0; guids[idx2]; idx2++)
    {
        HDEVINFO hDevInfo = SetupDiGetClassDevs(
            guids[idx2],
            nullptr, nullptr, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
        if (hDevInfo != INVALID_HANDLE_VALUE)
        {
            for (idx = 0;; idx++)
            {
                if (!getCDROMProperty(idx, hDevInfo, guids[idx2]))
                    break;
            }
            SetupDiDestroyDeviceInfoList(hDevInfo);
        }
    }

    for (idx2 = 0; scsinames[idx2]; idx2++)
    {
        int max = 10;
        for (idx = 0; idx < max; idx++)
        {
            TCHAR tmp[100];
            HANDLE h;
            _stprintf(tmp, L"\\\\.\\%s%d", scsinames[idx2], idx);
            h = CreateFile(tmp, GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                nullptr, OPEN_EXISTING, 0, nullptr);
            if (h != INVALID_HANDLE_VALUE)
            {
                adddrive(tmp, -1, -1, -1, -1, 2);
                CloseHandle(h);
                if (idx == max - 1)
                    max++;
            }
        }
    }
    if (g_curr_conf.win32_uaescsimode == UAESCSI_SPTISCAN)
    {
        Logger::Write(L"SCSI adapter enumeration..\n");
        scanscsi();
        Logger::Write(L"SCSI adapter enumeration ends\n");
    }
    return 1;
}

struct device_functions devicefunc_win32_spti =
{
    L"SPTI",
    open_scsi_bus, close_scsi_bus, open_scsi_device, close_scsi_device, info_device,
    execscsicmd_out, execscsicmd_in, execscsicmd_direct,
    0, 0, 0, 0, 0, 0, 0, 0, 0, check_isatapi, 0, 0
};